#include "QHexEditPrivate.h"

#include <QApplication>
#include <QClipboard>
#include <QPainter>

#include "ArrayCommand.h"

const int HEXCHARS_IN_LINE = 47;
const int BYTES_PER_LINE = 16;

QHexEditPrivate::QHexEditPrivate(QScrollArea* parent) : QWidget(parent) {
  _undoDataStack = new QUndoStack(this);
  _undoMaskStack = new QUndoStack(this);

  _scrollArea = parent;
  setOverwriteMode(true);

  QFont font("Monospace", 8, QFont::Normal, false);
  font.setFixedPitch(true);
  font.setStyleHint(QFont::Monospace);
  this->setFont(font);

  _size = 0;
  _horizonalSpacing = 3;
  resetSelection(0);
  setCursorPos(0);

  setFocusPolicy(Qt::StrongFocus);

  connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor()));
  _cursorTimer.setInterval(500);
  _cursorTimer.start();

  _textColor = QColor("#000000");
  _wildcardColor = QColor("#FF0000");
  _backgroundColor = QColor("#FFF8F0");
  _selectionColor = QColor("#C0C0C0");
  _overwriteMode = false;
  _wildcardEnabled = true;
  _keepSize = false;
}

void QHexEditPrivate::setData(const QByteArray& data, const QByteArray& mask) {
  _xData.setData(data);
  _xMask.setData(mask);
  _undoDataStack->clear();
  _undoMaskStack->clear();
  _initSize = _xData.size();
  adjust();
  setCursorPos(0);
  emit dataChanged();
}

QByteArray QHexEditPrivate::data() { return _xData.data(); }

QByteArray QHexEditPrivate::mask() { return _xMask.data(); }

void QHexEditPrivate::insert(int index, const QByteArray& ba,
                             const QByteArray& mask) {
  if (ba.length() > 0) {
    if (_overwriteMode) {
      _undoDataStack->push(new ArrayCommand(&_xData, ArrayCommand::replace,
                                            index, ba, ba.length()));
      _undoMaskStack->push(new ArrayCommand(&_xMask, ArrayCommand::replace,
                                            index, mask, mask.length()));
    } else {
      _undoDataStack->push(new ArrayCommand(&_xData, ArrayCommand::insert,
                                            index, ba, ba.length()));
      _undoMaskStack->push(new ArrayCommand(&_xMask, ArrayCommand::insert,
                                            index, mask, mask.length()));
    }

    emit dataChanged();
    emit dataEdited();
  }
}

void QHexEditPrivate::insert(int index, char ch, char mask) {
  _undoDataStack->push(
      new CharCommand(&_xData, CharCommand::insert, index, ch));
  _undoMaskStack->push(
      new CharCommand(&_xMask, CharCommand::insert, index, mask));
  emit dataChanged();
  emit dataEdited();
}

void QHexEditPrivate::remove(int index, int len) {
  if (len > 0) {
    if (len == 1) {
      if (_overwriteMode) {
        _undoDataStack->push(
            new CharCommand(&_xData, CharCommand::replace, index, char(0)));
        _undoMaskStack->push(
            new CharCommand(&_xMask, CharCommand::replace, index, char(0)));
      } else {
        _undoDataStack->push(
            new CharCommand(&_xData, CharCommand::remove, index, char(0)));
        _undoMaskStack->push(
            new CharCommand(&_xMask, CharCommand::remove, index, char(0)));
      }
    } else {
      QByteArray ba = QByteArray(len, char(0));
      if (_overwriteMode) {
        _undoDataStack->push(new ArrayCommand(&_xData, ArrayCommand::replace,
                                              index, ba, ba.length()));
        _undoMaskStack->push(new ArrayCommand(&_xMask, ArrayCommand::replace,
                                              index, ba, ba.length()));
      } else {
        _undoDataStack->push(
            new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len));
        _undoMaskStack->push(
            new ArrayCommand(&_xMask, ArrayCommand::remove, index, ba, len));
      }
    }

    emit dataChanged();
    emit dataEdited();
  }
}

void QHexEditPrivate::replace(int index, char ch, char mask) {
  _undoDataStack->push(
      new CharCommand(&_xData, CharCommand::replace, index, ch));
  _undoMaskStack->push(
      new CharCommand(&_xMask, CharCommand::replace, index, mask));
  resetSelection();
  emit dataChanged();
  emit dataEdited();
}

void QHexEditPrivate::replace(int index, const QByteArray& ba,
                              const QByteArray& mask) {
  _undoDataStack->push(
      new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()));
  _undoMaskStack->push(new ArrayCommand(&_xMask, ArrayCommand::replace, index,
                                        mask, mask.length()));
  resetSelection();
  emit dataChanged();
  emit dataEdited();
}

void QHexEditPrivate::replace(int pos, int len, const QByteArray& after,
                              const QByteArray& mask) {
  _undoDataStack->push(
      new ArrayCommand(&_xData, ArrayCommand::replace, pos, after, len));
  _undoMaskStack->push(
      new ArrayCommand(&_xMask, ArrayCommand::replace, pos, mask, len));
  resetSelection();
  emit dataChanged();
  emit dataEdited();
}

void QHexEditPrivate::fill(int index, const QByteArray& ba,
                           const QByteArray& mask) {
  int dataSize = _xData.size();
  if (index >= dataSize) return;
  int repeat = dataSize / ba.size() + 1;
  QByteArray fillData = ba.repeated(repeat);
  fillData.resize(dataSize);
  fillData = fillData.toHex();
  QByteArray fillMask = mask.repeated(repeat);
  fillMask.resize(dataSize);
  fillMask = fillMask.toHex();
  QByteArray origData = _xData.data().mid(index).toHex();
  for (int i = 0; i < dataSize * 2; i++)
    if (fillMask[i] == '1') fillData[i] = origData[i];
  this->replace(index, QByteArray().fromHex(fillData),
                QByteArray().fromHex(fillMask));
}

void QHexEditPrivate::setOverwriteMode(bool overwriteMode) {
  _overwriteMode = overwriteMode;
}

bool QHexEditPrivate::overwriteMode() { return _overwriteMode; }

void QHexEditPrivate::setWildcardEnabled(bool enabled) {
  _wildcardEnabled = enabled;
}

bool QHexEditPrivate::wildcardEnabled() { return _wildcardEnabled; }

void QHexEditPrivate::setKeepSize(bool enabled) { _keepSize = enabled; }

bool QHexEditPrivate::keepSize() { return _keepSize; }

void QHexEditPrivate::setHorizontalSpacing(int x) {
  _horizonalSpacing = x;
  adjust();
  setCursorPos(cursorPos());
  this->update();
}

int QHexEditPrivate::horizontalSpacing() { return _horizonalSpacing; }

void QHexEditPrivate::setTextColor(QColor color) { _textColor = color; }

QColor QHexEditPrivate::textColor() { return _textColor; }

void QHexEditPrivate::setWildcardColor(QColor color) { _wildcardColor = color; }

QColor QHexEditPrivate::wildcardColor() { return _wildcardColor; }

void QHexEditPrivate::setBackgroundColor(QColor color) {
  _backgroundColor = color;
}

QColor QHexEditPrivate::backgroundColor() { return _backgroundColor; }

void QHexEditPrivate::setSelectionColor(QColor color) {
  _selectionColor = color;
}

QColor QHexEditPrivate::selectionColor() { return _selectionColor; }

void QHexEditPrivate::redo() {
  if (!_undoDataStack->canRedo() || !_undoMaskStack->canRedo()) return;
  _undoDataStack->redo();
  _undoMaskStack->redo();
  emit dataChanged();
  emit dataEdited();
  setCursorPos(_cursorPosition);
  update();
}

void QHexEditPrivate::undo() {
  if (!_undoDataStack->canUndo() || !_undoMaskStack->canUndo()) return;
  _undoDataStack->undo();
  _undoMaskStack->undo();
  emit dataChanged();
  emit dataEdited();
  setCursorPos(_cursorPosition);
  update();
}

void QHexEditPrivate::focusInEvent(QFocusEvent* event) {
  ensureVisible();
  QWidget::focusInEvent(event);
}

void QHexEditPrivate::resizeEvent(QResizeEvent* event) {
  adjust();
  QWidget::resizeEvent(event);
}

void QHexEditPrivate::keyPressEvent(QKeyEvent* event) {
  int charX = (_cursorX - _xPosHex) / _charWidth;
  int posX = (charX / 3) * 2 + (charX % 3);
  int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2;

  /*****************************************************************************/
  /* Cursor movements */
  /*****************************************************************************/

  if (event->matches(QKeySequence::MoveToNextChar)) {
    setCursorPos(_cursorPosition + 1);
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToPreviousChar)) {
    setCursorPos(_cursorPosition - 1);
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToEndOfLine)) {
    setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE - 1));
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToStartOfLine)) {
    setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)));
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToPreviousLine)) {
    setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE));
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToNextLine)) {
    setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE));
    resetSelection(_cursorPosition);
  }

  if (event->matches(QKeySequence::MoveToNextPage)) {
    setCursorPos(_cursorPosition +
                 (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 *
                  BYTES_PER_LINE));
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToPreviousPage)) {
    setCursorPos(_cursorPosition -
                 (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 *
                  BYTES_PER_LINE));
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToEndOfDocument)) {
    setCursorPos(_xData.size() * 2);
    resetSelection(_cursorPosition);
  }
  if (event->matches(QKeySequence::MoveToStartOfDocument)) {
    setCursorPos(0);
    resetSelection(_cursorPosition);
  }

  /*****************************************************************************/
  /* Select commands */
  /*****************************************************************************/
  if (event->matches(QKeySequence::SelectAll)) {
    resetSelection(0);
    setSelection(2 * _xData.size() + 1);
  }
  if (event->matches(QKeySequence::SelectNextChar)) {
    int pos = _cursorPosition + 1;
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectPreviousChar)) {
    int pos = _cursorPosition - 1;
    setSelection(pos);
    setCursorPos(pos);
  }
  if (event->matches(QKeySequence::SelectEndOfLine)) {
    int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) +
              (2 * BYTES_PER_LINE);
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectStartOfLine)) {
    int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE));
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectPreviousLine)) {
    int pos = _cursorPosition - (2 * BYTES_PER_LINE);
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectNextLine)) {
    int pos = _cursorPosition + (2 * BYTES_PER_LINE);
    setCursorPos(pos);
    setSelection(pos);
  }

  if (event->matches(QKeySequence::SelectNextPage)) {
    int pos = _cursorPosition +
              (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 *
               BYTES_PER_LINE);
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectPreviousPage)) {
    int pos = _cursorPosition -
              (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 *
               BYTES_PER_LINE);
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectEndOfDocument)) {
    int pos = _xData.size() * 2;
    setCursorPos(pos);
    setSelection(pos);
  }
  if (event->matches(QKeySequence::SelectStartOfDocument)) {
    int pos = 0;
    setCursorPos(pos);
    setSelection(pos);
  }

  /*****************************************************************************/
  /* Edit Commands */
  /*****************************************************************************/
  /* Hex input */
  int key = int(event->text().toLower()[0].toLatin1());
  if ((key >= '0' && key <= '9') || (key >= 'a' && key <= 'f') ||
      (_wildcardEnabled && key == '?')) {
    if (getSelectionBegin() != getSelectionEnd()) {
      posBa = getSelectionBegin();
      remove(posBa, getSelectionEnd() - posBa);
      setCursorPos(2 * posBa);
      resetSelection(2 * posBa);
    }

    // If insert mode, then insert a byte
    if (_overwriteMode == false) {
      if (_keepSize && _xData.size() >= _initSize) {
        QWidget::keyPressEvent(event);
        return;
      }
      if ((charX % 3) == 0) insert(posBa, char(0), _wildcardEnabled);
    }

    // Change content
    if (_xData.size() > 0) {
      QByteArray hexMask = _xMask.data().mid(posBa, 1).toHex();
      QByteArray hexValue = _xData.data().mid(posBa, 1).toHex();
      if (key == '?')  // wildcard
      {
        if ((charX % 3) == 0)
          hexMask[0] = '1';
        else
          hexMask[1] = '1';
      } else {
        if ((charX % 3) == 0) {
          hexValue[0] = key;
          hexMask[0] = '0';
        } else {
          hexValue[1] = key;
          hexMask[1] = '0';
        }
      }
      replace(posBa, QByteArray().fromHex(hexValue)[0],
              QByteArray().fromHex(hexMask)[0]);

      setCursorPos(_cursorPosition + 1);
      resetSelection(_cursorPosition);
    }
  }

  /* Cut & Paste */
  if (event->matches(QKeySequence::Cut)) {
    QString result;
    for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) {
      QString byte = _xData.data().mid(idx, 1).toHex();
      QString mask = _xMask.data().mid(idx, 1).toHex();
      if (mask[0] == '1')
        result += "?";
      else
        result += byte[0];
      if (mask[1] == '1')
        result += "?";
      else
        result += byte[1];
      result += " ";
    }
    remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin());
    QClipboard* clipboard = QApplication::clipboard();
    clipboard->setText(result.toUpper());
    setCursorPos(getSelectionBegin() + 2);
    resetSelection(getSelectionBegin() + 2);
  }

  if (event->matches(QKeySequence::Paste)) {
    QClipboard* clipboard = QApplication::clipboard();
    QString convert;
    QString pattern = clipboard->text();
    for (int i = 0; i < pattern.length(); i++) {
      QChar ch = pattern[i].toLower();
      if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') ||
          (_wildcardEnabled && ch == '?'))
        convert += ch;
    }
    if (convert.length() % 2)  // odd length
      convert += "0";
    if (_keepSize && convert.length() / 2 >= _initSize)
      convert.truncate((_initSize - _xData.size()) * 2 - 1);
    QByteArray data(convert.length(), 0);
    QByteArray mask(data.length(), 0);
    for (int i = 0; i < convert.length(); i++) {
      if (convert[i] == '?') {
        data[i] = '0';
        mask[i] = '1';
      } else {
        data[i] = convert[i].toLatin1();
        mask[i] = '0';
      }
    }
    data = QByteArray().fromHex(data);
    mask = QByteArray().fromHex(mask);
    insert(_cursorPosition / 2, data, mask);
    setCursorPos(_cursorPosition + 2 * data.length());
    resetSelection(getSelectionBegin());
  }

  /* Delete char */
  if (event->matches(QKeySequence::Delete)) {
    if (getSelectionBegin() != getSelectionEnd()) {
      posBa = getSelectionBegin();
      remove(posBa, getSelectionEnd() - posBa);
      setCursorPos(2 * posBa);
      resetSelection(2 * posBa);
    } else {
      if (_overwriteMode)
        replace(posBa, char(0), char(0));
      else
        remove(posBa, 1);
    }
  }

  /* Backspace */
  if ((event->key() == Qt::Key_Backspace) &&
      (event->modifiers() == Qt::NoModifier)) {
    if (getSelectionBegin() != getSelectionEnd()) {
      posBa = getSelectionBegin();
      remove(posBa, getSelectionEnd() - posBa);
      setCursorPos(2 * posBa);
      resetSelection(2 * posBa);
    } else if (_cursorPosition == 1) {
      if (getSelectionBegin() != getSelectionEnd()) {
        posBa = getSelectionBegin();
        remove(posBa, getSelectionEnd() - posBa);
        setCursorPos(2 * posBa);
        resetSelection(2 * posBa);
      } else {
        if (_overwriteMode)
          replace(posBa, char(0), char(0));
        else
          remove(posBa, 1);
      }
      setCursorPos(0);
    } else if (posBa > 0) {
      int delta = 1;
      if (_cursorPosition % 2)  // odd cursor position
        delta = 0;
      if (_overwriteMode)
        replace(posBa - delta, char(0), char(0));
      else
        remove(posBa - delta, 1);
      setCursorPos(_cursorPosition - 1 - delta);
    }
  }

  /* undo */
  if (event->matches(QKeySequence::Undo)) {
    undo();
  }

  /* redo */
  if (event->matches(QKeySequence::Redo)) {
    redo();
  }

  if (event->matches(QKeySequence::Copy)) {
    QString result;
    for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) {
      QString byte = _xData.data().mid(idx, 1).toHex();
      if (!byte.length()) break;
      QString mask = _xMask.data().mid(idx, 1).toHex();
      if (mask[0] == '1')
        result += "?";
      else
        result += byte[0];
      if (mask[1] == '1')
        result += "?";
      else
        result += byte[1];
      result += " ";
    }
    QClipboard* clipboard = QApplication::clipboard();
    if (result.length()) clipboard->setText(result.toUpper().trimmed());
  }

  // Switch between insert/overwrite mode
  if ((event->key() == Qt::Key_Insert) &&
      (event->modifiers() == Qt::NoModifier)) {
    _overwriteMode = !_overwriteMode;
    setCursorPos(_cursorPosition);
    overwriteModeChanged(_overwriteMode);
  }

  ensureVisible();
  update();
  QWidget::keyPressEvent(event);
}

void QHexEditPrivate::mouseMoveEvent(QMouseEvent* event) {
  _blink = false;
  update();
  int actPos = cursorPos(event->pos());
  setCursorPos(actPos);
  setSelection(actPos);
  QWidget::mouseMoveEvent(event);
}

void QHexEditPrivate::mousePressEvent(QMouseEvent* event) {
  _blink = false;
  update();
  int cPos = cursorPos(event->pos());
  if ((event->modifiers() & Qt::ShiftModifier) == Qt::ShiftModifier) {
    setCursorPos(cPos);
    setSelection(cPos);
  } else {
    resetSelection(cPos);
    setCursorPos(cPos);
  }
  QWidget::mousePressEvent(event);
}

void QHexEditPrivate::paintEvent(QPaintEvent* event) {
  QPainter painter(this);
  painter.setFont(font());

  painter.fillRect(event->rect(), _backgroundColor);

  // calc position
  int firstLineIdx =
      ((event->rect().top() / _charHeight) - _charHeight) * BYTES_PER_LINE;
  if (firstLineIdx < 0) firstLineIdx = 0;
  int lastLineIdx =
      ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE;
  if (lastLineIdx > _xData.size()) lastLineIdx = _xData.size();
  int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight;

  // paint hex area
  QByteArray hexBa(
      _xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex());
  if (_wildcardEnabled) {
    QByteArray hexMask(_xMask.data()
                           .mid(firstLineIdx, lastLineIdx - firstLineIdx + 1)
                           .toHex());
    for (int i = 0; i < hexBa.size(); i++)
      if (hexMask[i] == '1') hexBa[i] = '?';
  }

  for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx;
       lineIdx += BYTES_PER_LINE, yPos += _charHeight) {
    QString hex;
    int xPos = _xPosHex;
    for (int colIdx = 0;
         ((lineIdx + colIdx) < _xData.size() && (colIdx < BYTES_PER_LINE));
         colIdx++) {
      int posBa = lineIdx + colIdx;
      if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) {
        painter.setBackground(_selectionColor);
        painter.setBackgroundMode(Qt::OpaqueMode);
      } else {
        painter.setBackground(_backgroundColor);
        painter.setBackgroundMode(Qt::TransparentMode);
      }

      // render hex value
      painter.setPen(_textColor);
      if (colIdx == 0) {
        hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2).toUpper();
        for (int i = 0; i < hex.length(); i++) {
          if (hex[i] == '?')  // wildcard
            painter.setPen(_wildcardColor);
          else
            painter.setPen(_textColor);
          painter.drawText(xPos + _charWidth * i, yPos, QString(hex[i]));
        }
        xPos += 2 * _charWidth;
      } else {
        if (getSelectionBegin() == posBa) {
          hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).toUpper();
          xPos += _charWidth;
        } else
          hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2)
                    .prepend(" ")
                    .toUpper();
        for (int i = 0; i < hex.length(); i++) {
          if (hex[i] == '?')  // wildcard
            painter.setPen(_wildcardColor);
          else
            painter.setPen(_textColor);
          painter.drawText(xPos + _charWidth * i, yPos, QString(hex[i]));
        }
        xPos += hex.length() * _charWidth;
      }
    }
  }
  painter.setBackgroundMode(Qt::TransparentMode);
  painter.setPen(this->palette().color(QPalette::WindowText));

  // paint cursor
  if (_blink && hasFocus()) {
    if (_overwriteMode)
      painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2,
                       this->palette().color(QPalette::WindowText));
    else
      painter.fillRect(_cursorX, _cursorY, 2, _charHeight,
                       this->palette().color(QPalette::WindowText));
  }

  if (_size != _xData.size()) {
    _size = _xData.size();
    emit currentSizeChanged(_size);
  }
}

void QHexEditPrivate::setCursorPos(int position) {
  // delete cursor
  _blink = false;
  update();

  // cursor in range?
  if (_overwriteMode) {
    if (position > (_xData.size() * 2 - 1)) position = _xData.size() * 2 - 1;
  } else {
    if (position > (_xData.size() * 2)) position = _xData.size() * 2;
  }

  if (position < 0) position = 0;

  // calc position
  _cursorPosition = position;
  _cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4;
  int x = (position % (2 * BYTES_PER_LINE));
  _cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex;

  // immiadately draw cursor
  _blink = true;
  update();
  emit currentAddressChanged(_cursorPosition / 2);
}

int QHexEditPrivate::cursorPos(QPoint pos) {
  int result = -1;
  // find char under cursor
  if ((pos.x() >= _xPosHex) &&
      (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth))) {
    int x = (pos.x() - _xPosHex) / _charWidth;
    if ((x % 3) == 0)
      x = (x / 3) * 2;
    else
      x = ((x / 3) * 2) + 1;
    int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE;
    result = x + y;
  }
  return result;
}

int QHexEditPrivate::cursorPos() { return _cursorPosition; }

void QHexEditPrivate::resetSelection() {
  _selectionBegin = _selectionInit;
  _selectionEnd = _selectionInit;
}

void QHexEditPrivate::resetSelection(int pos) {
  if (pos < 0) pos = 0;
  pos++;
  pos = pos / 2;
  _selectionInit = pos;
  _selectionBegin = pos;
  _selectionEnd = pos;
}

void QHexEditPrivate::setSelection(int pos) {
  if (pos < 0) pos = 0;
  pos++;
  pos = pos / 2;
  if (pos >= _selectionInit) {
    _selectionEnd = pos;
    _selectionBegin = _selectionInit;
  } else {
    _selectionBegin = pos;
    _selectionEnd = _selectionInit;
  }
}

int QHexEditPrivate::getSelectionBegin() { return _selectionBegin; }

int QHexEditPrivate::getSelectionEnd() { return _selectionEnd; }

void QHexEditPrivate::updateCursor() {
  if (_blink)
    _blink = false;
  else
    _blink = true;
  update(_cursorX, _cursorY, _charWidth, _charHeight);
}

void QHexEditPrivate::adjust() {
  QFontMetrics metrics(this->font());
  _charWidth = metrics.width(QLatin1Char('9'));
  _charHeight = metrics.height();

  _xPosHex = _horizonalSpacing;

  // tell QAbstractScollbar, how big we are
  setMinimumHeight(((_xData.size() / 16 + 1) * _charHeight) + 5);
  setMinimumWidth(_xPosHex + HEXCHARS_IN_LINE * _charWidth);

  update();
}

void QHexEditPrivate::ensureVisible() {
  // scrolls to cursorx, cusory (which are set by setCursorPos)
  // x-margin is 3 pixels, y-margin is half of charHeight
  _scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight / 2, 3,
                             _charHeight / 2 + 2);
}
